interface HeaderObj {
    [key: string]: string
}

export default class Http {
    private static _instance: Http = null!;
    public static get instance() {
        if (!this._instance) {
            this._instance = new Http();
        }
        return this._instance;
    }
    headers: HeaderObj = {};
    baseUrl: string = `http://baidu.com`;

    /** 请求前token为空 */
    onTokenPostFail() { }

    /** 请求失败，token为空回调 */
    onTokenRespondFail() { }

    /** 提示接口返回错误 */
    ErrorHandler(ret: any) { }

    get(url: string, params?: Object, token: boolean = true, showErr: boolean = true): Promise<any> {
        return new Promise((resolve, reject) => {

            let xhr = new XMLHttpRequest();
            xhr.timeout = 5000;
            let requestURL = url;
            if (params) {
                requestURL = requestURL + this.paramFormat2UrlStr(params);
            }
            let baseUrl = this.baseUrl;
            if (baseUrl && requestURL.indexOf(`http`) == -1) {
                if (baseUrl[baseUrl.length - 1] == `/`) {
                    baseUrl = baseUrl.substring(0, baseUrl.length - 1);
                }
                if (requestURL[0] == `/`) {
                    requestURL = requestURL.substring(1, requestURL.length);
                }
                requestURL = baseUrl + `/` + requestURL;
            }
            params = params ? params : {};
            console.log(`%c[Http] --> Get: ${url} \n params: ${JSON.stringify(params)} token: ${token}`, `color:#1C00CF`);
            xhr.open("GET", requestURL, true);
            xhr.setRequestHeader(`Content-Type`, `application/json`);

            if (token) {
                if (Object.keys(this.headers).length > 0) {
                    for (let key in this.headers) {
                        xhr.setRequestHeader(key, this.headers[key]);
                    }
                } else {
                    token = false;
                }
                if (!token) {
                    this.onTokenPostFail();
                    return;
                }
            }
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
                    console.log(`%c[Http] --> Respon: ${requestURL}\nres (len: ${xhr.responseText.length}):${xhr.responseText}`, `color:#6bd1c7`);
                    try {
                        let ret = xhr.responseText;
                        if (JSON.parse(ret).result == 401) {
                            this.onTokenRespondFail();
                        } else if (JSON.parse(ret).result == -1) {
                            showErr && this.ErrorHandler(ret)
                        }
                        resolve(JSON.parse(ret));
                        return;
                    } catch (e) {
                        reject(e);
                    }
                } else {
                    if (xhr.readyState != 2 && xhr.readyState != 3) {
                        console.log(`[Http] --> fail(readyState：${xhr.readyState})  status：${xhr.status}`);
                        reject(`${xhr.readyState}: ${xhr.status}`);
                    }
                }
            };
            xhr.ontimeout = () => {
                console.log(`[Http] --> time out(readyState：${xhr.readyState})  status：${xhr.status}`);
                reject(`${xhr.readyState}: ${xhr.status}`);
            }
            xhr.onerror = () => {
                console.log(`[Http] --> onerror(readyState：${xhr.readyState})  status：${xhr.status}`);
                reject(`${xhr.readyState}: ${xhr.status}`);
            }
            xhr.send();
            return xhr;
        });
    }

    post(url: string, body?: any, queryParam?: Object, token: boolean = true, showErr: boolean = true): Promise<any> {
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.timeout = 30000;
            let requestURL = url;
            if (queryParam)
                requestURL = requestURL + this.paramFormat2UrlStr(queryParam);
            let baseUrl = this.baseUrl;
            if (baseUrl && requestURL.indexOf(`http`) == -1) {
                if (baseUrl[baseUrl.length - 1] == `/`) {
                    baseUrl = baseUrl.substring(0, baseUrl.length - 1);
                }
                if (requestURL[0] == `/`) {
                    requestURL = requestURL.substring(1, requestURL.length);
                }
                requestURL = baseUrl + `/` + requestURL;
            }
            console.log(`%c [Http] --> post:\nurl: ${requestURL} \n body: ${JSON.stringify(body)}\n token:${token}\n header:${JSON.stringify(this.headers)}`, `color:#6bd1c7`);
            xhr.open(`POST`, requestURL, true);
            xhr.setRequestHeader(`Content-Type`, `application/json`);

            if (token) {
                if (Object.keys(this.headers).length > 0) {
                    for (let key in this.headers) {
                        xhr.setRequestHeader(key, this.headers[key]);
                    }
                } else {
                    token = false;
                }
                if (!token) {
                    this.onTokenPostFail();
                    return;
                }
            }

            xhr.onreadystatechange = () => {
                // console.log(`%c [Http] --> post Respond!\nurl: ${requestURL}\n readyState: ${xhr.readyState}\n status:${xhr.status}\n`);
                if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
                    try {
                        let ret = xhr.responseText;
                        console.log(`%c [Http] --> post Respond!\nurl: ${requestURL}\n res:${ret}`, `color: #C41A16`);
                        if (JSON.parse(ret).result == 401) {
                            this.onTokenRespondFail();
                            return;
                        } else if (JSON.parse(ret).result == -1) {
                            showErr && this.ErrorHandler(ret)
                        }
                        resolve(JSON.parse(ret));

                        // console.log(`%c [Http] -->`, `color: #C41A16`, JSON.parse(ret));
                        return;
                    } catch (e) {
                        // console.error(`[Http] --> post fail! ${e}`);
                        reject(e);
                    }
                } else {
                    if (xhr.readyState != 2 && xhr.readyState != 3) {
                        // console.log(`[Http] --> post fail(readyState：${xhr.readyState}) status：${xhr.status}`);
                        reject(`${xhr.readyState}: ${xhr.status}`);
                    }
                };
            };

            xhr.ontimeout = () => {
                reject();
            }
            // const b = new FormData();
            // for (let i in body) {
            //     b.append(i, body[i]);
            // }
            const b = JSON.stringify(body);
            xhr.send(b);
            return xhr;
        });
    }

    delete(url: string, body?: any, queryParam?: Object, token: boolean = true, showErr: boolean = true): Promise<any> {
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.timeout = 30000;
            let requestURL = url;
            if (queryParam)
                requestURL = requestURL + this.paramFormat2UrlStr(queryParam);
            let baseUrl = this.baseUrl;
            if (baseUrl && requestURL.indexOf(`http`) == -1) {
                if (baseUrl[baseUrl.length - 1] == `/`) {
                    baseUrl = baseUrl.substring(0, baseUrl.length - 1);
                }
                if (requestURL[0] == `/`) {
                    requestURL = requestURL.substring(1, requestURL.length);
                }
                requestURL = baseUrl + `/` + requestURL;
            }
            console.log(`%c [Http] --> send:\nurl: ${requestURL} \n body: ${JSON.stringify(body)}\n token:${token}\n header:${JSON.stringify(this.headers)}`, `color:#6bd1c7`);
            xhr.open(`delete`, requestURL, true);
            xhr.setRequestHeader(`Content-Type`, `application/json`);

            if (token) {
                if (Object.keys(this.headers).length > 0) {
                    for (let key in this.headers) {
                        xhr.setRequestHeader(key, this.headers[key]);
                    }
                } else {
                    token = false;
                }
                if (!token) {
                    this.onTokenPostFail();
                    return;
                }
            }

            xhr.onreadystatechange = () => {
                // console.log(`%c [Http] --> post Respond!\nurl: ${requestURL}\n readyState: ${xhr.readyState}\n status:${xhr.status}\n`);
                if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
                    try {
                        let ret = xhr.responseText;
                        console.log(`%c [Http] --> post Respond!\nurl: ${requestURL}\n res:${ret}`, `color: #C41A16`);
                        if (JSON.parse(ret).result == 401) {
                            this.onTokenRespondFail();
                            return;
                        } else if (JSON.parse(ret).result == -1) {
                            showErr && this.ErrorHandler(ret)
                        }
                        resolve(JSON.parse(ret));

                        // console.log(`%c [Http] -->`, `color: #C41A16`, JSON.parse(ret));
                        return;
                    } catch (e) {
                        // console.error(`[Http] --> post fail! ${e}`);
                        reject(e);
                    }
                } else {
                    if (xhr.readyState != 2 && xhr.readyState != 3) {
                        // console.log(`[Http] --> post fail(readyState：${xhr.readyState}) status：${xhr.status}`);
                        reject(`${xhr.readyState}: ${xhr.status}`);
                    }
                };
            };

            xhr.ontimeout = () => {
                reject();
            }
            // const b = new FormData();
            // for (let i in body) {
            //     b.append(i, body[i]);
            // }
            const b = JSON.stringify(body);
            xhr.send(b);
            return xhr;
        });
    }

    download(url: string, params?: Object): Promise<any> {
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.timeout = 5000;
            let requestURL = url;
            if (params)
                requestURL = requestURL + this.paramFormat2UrlStr(params);
            xhr.responseType = `arraybuffer`;
            xhr.open(`GET`, requestURL, true);
            // if (cc.sys.isNative) {
            //     xhr.setRequestHeader(`Accept-Encoding`, `gzip,deflate`, `text/html;charset=UTF-8`);
            // }

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
                    let buffer = xhr.response;
                    let dataview = new DataView(buffer);
                    let ints = new Uint8Array(buffer.byteLength);
                    for (let i = 0; i < ints.length; ++i) {
                        ints[i] = dataview.getUint8(i);
                    }
                    resolve(ints);
                } else {
                    reject(`${xhr.readyState}: ${xhr.status}`);
                };
                xhr.send();
                return xhr;
            };
        });
    }

    paramFormat2UrlStr(params: Object) {
        let str = ``;
        if (typeof (params) == `object`) {
            if (Object.keys(params).length > 0) {
                str += `?`;
            }
            for (let key in params) {
                if (str != `?`) {
                    str += `&`;
                }
                str += `${key}=${params[key]}`;
            }
        }
        return str;
    }

    paramJoint(params: Object) {
        let str = ``;
        if (typeof (params) == `object`) {
            for (let key in params) {
                if (str != ``) {
                    str += `&`;
                }
                str += `${key}=${params[key]}`;
            }
        }
        return str;
    }

    setHeaders(obj: HeaderObj) {
        this.headers = obj;
    }
}
